import os
from enum import Enum
from importlib.util import spec_from_file_location, module_from_spec
from typing import Optional, Callable, List, Dict, Any

import pandas as pd
from rich.console import Console
from tqdm import tqdm


from dataclasses import dataclass


@dataclass
class TrainParam:
    delete_file_suffix=0.95
    is_train=True
    console:Console=Console()
    origin_df:pd.DataFrame=None

class FlowEnum(Enum):
    step1_read_data=(0, '读取数据')
    step2_check_data=(1, '检查数据')
    step3_delete_data=(2, '删除空值因包不完整导致的空值')
    step4_delete_file_data=(3, '删除Get请求为文件的数据、post或其他数据内容类型是文件的数据,该步骤保存df，供后续预测后提取原始数据')
    step5_split_uri_to_cols=(4, "从http.uri中分离 'http.uri_domain', 'http.uri_path', 'http.uri_params'")
    step6_pre_handle_str=(5, "对所有文本字段进行预处理")
    step7_fill_num=(6, "填充数值类型")
    step8_pre_handle_array=(7, "处理数组类型的字段")
    step9_fill_bool_with_num=(8, "用数字替换布尔值")
    step10_train_or_predict=(8, "训练或者预测流程")
FuncType = Callable[[Optional[pd.DataFrame],Optional[TrainParam],],Optional[pd.DataFrame]]
RunCallbackType = Callable[[Dict[str, Any]], None]
class FlowInterrupt(Exception):
    pass
class FlowPipline:
    train_steps:List[tuple[FlowEnum,FuncType]] = []
    predict_steps:List[tuple[FlowEnum,FuncType]] = []
    @classmethod
    def bind(cls, flow:FlowEnum, is_train:bool, is_predict:bool):
        def decorator(func:FuncType):
            if is_train:
                cls.train_steps.append((flow, func))
                cls.train_steps.sort(key=lambda x:x[0].value[0])
            if is_predict:
                cls.predict_steps.append((flow, func))
                cls.predict_steps.sort(key=lambda x:x[0].value[0])
            return func
        return decorator
    @classmethod
    def run(cls,  params: TrainParam = TrainParam()) -> Optional[pd.DataFrame]:
        steps_dir = os.path.dirname(__file__)
        for filename in os.listdir(steps_dir):
            if filename.endswith('.py') and filename != os.path.basename(__file__):
                print(filename)
                module_name = filename[:-3]
                module_path = os.path.join(steps_dir, filename)
                spec = spec_from_file_location(module_name, module_path)
                module = module_from_spec(spec)
                spec.loader.exec_module(module)
        if params.is_train:
            steps=[step for step in cls.train_steps if isinstance(step[0], FlowEnum)]
        else:
            steps=[step for step in cls.predict_steps if isinstance(step[0], FlowEnum)]
        try:
            tqdm.pandas()
            df = pd.DataFrame()
            for flow_enum, func in steps:
                params.console.clear()
                order=flow_enum.value[0]
                desc=flow_enum.value[1]
                params.console.print(f"执行第{order}个步骤: {desc}")
                df = func(df, params)
            return df
        except FlowInterrupt as e:
            print(e)
            return None